﻿using JNPF.Common.Const;
using JNPF.Common.Core.Captcha.General;
using JNPF.Common.Core.Manager;
using JNPF.Common.Core.Security;
using JNPF.Common.Dtos.OAuth;
using JNPF.Common.Enum;
using JNPF.Common.Extension;
using JNPF.Common.Models.User;
using JNPF.Common.Net;
using JNPF.Common.Options;
using JNPF.Common.Security;
using JNPF.DataEncryption;
using JNPF.DependencyInjection;
using JNPF.DynamicApiController;
using JNPF.EventBus;
using JNPF.EventHandler;
using JNPF.FriendlyException;
using JNPF.Logging.Attributes;
using JNPF.OAuth.Dto;
using JNPF.OAuth.Model;
using JNPF.RemoteRequest.Extensions;
using JNPF.Systems.Entitys.Enum;
using JNPF.Systems.Entitys.Model.SysConfig;
using JNPF.Systems.Entitys.Permission;
using JNPF.Systems.Entitys.System;
using JNPF.Systems.Interfaces.System;
using JNPF.UnifyResult;
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using SqlSugar;

namespace JNPF.OAuth;

/// <summary>
/// 业务实现：身份认证模块.
/// </summary>
[ApiDescriptionSettings(Tag = "OAuth", Name = "OAuth", Order = 160)]
[Route("api/[controller]")]
public class OAuthService : IDynamicApiController, ITransient
{
    /// <summary>
    /// 用户仓储.
    /// </summary>
    private readonly ISqlSugarRepository<UserEntity> _userRepository;

    /// <summary>
    /// 功能模块.
    /// </summary>
    private readonly IModuleService _moduleService;

    /// <summary>
    /// 功能按钮.
    /// </summary>
    private readonly IModuleButtonService _moduleButtonService;

    /// <summary>
    /// 功能列.
    /// </summary>
    private readonly IModuleColumnService _columnService;

    /// <summary>
    /// 功能数据权限计划.
    /// </summary>
    private readonly IModuleDataAuthorizeSchemeService _moduleDataAuthorizeSchemeService;

    /// <summary>
    /// 功能表单.
    /// </summary>
    private readonly IModuleFormService _formService;

    /// <summary>
    /// 系统配置.
    /// </summary>
    private readonly ISysConfigService _sysConfigService;

    /// <summary>
    /// 验证码处理程序.
    /// </summary>
    private readonly IGeneralCaptcha _captchaHandler;

    /// <summary>
    /// 数据库配置选项.
    /// </summary>
    private readonly ConnectionStringsOptions _connectionStrings;

    /// <summary>
    /// 多租户配置选项.
    /// </summary>
    private readonly TenantOptions _tenant;

    /// <summary>
    /// Http上下文.
    /// </summary>
    private readonly IHttpContextAccessor _httpContextAccessor;

    /// <summary>
    /// 缓存管理.
    /// </summary>
    private readonly ICacheManager _cacheManager;

    /// <summary>
    /// 用户管理.
    /// </summary>
    private readonly IUserManager _userManager;

    /// <summary>
    /// 事件总线.
    /// </summary>
    private readonly IEventPublisher _eventPublisher;

    /// <summary>
    /// SqlSugarClient客户端.
    /// </summary>
    private SqlSugarClient _sqlSugarClient;

    /// <summary>
    /// 初始化一个<see cref="OAuthService"/>类型的新实例.
    /// </summary>
    public OAuthService(
        IGeneralCaptcha captchaHandler,
        ISqlSugarRepository<UserEntity> userRepository,
        IModuleService moduleService,
        IModuleButtonService moduleButtonService,
        IModuleColumnService columnService,
        IModuleDataAuthorizeSchemeService moduleDataAuthorizeSchemeService,
        IModuleFormService formService,
        ISysConfigService sysConfigService,
        IOptions<ConnectionStringsOptions> connectionOptions,
        IOptions<TenantOptions> tenantOptions,
        ISqlSugarClient sqlSugarClient,
        IHttpContextAccessor httpContextAccessor,
        ICacheManager cacheManager,
        IUserManager userManager,
        IEventPublisher eventPublisher)
    {
        _captchaHandler = captchaHandler;
        _userRepository = userRepository;
        _moduleService = moduleService;
        _moduleButtonService = moduleButtonService;
        _columnService = columnService;
        _moduleDataAuthorizeSchemeService = moduleDataAuthorizeSchemeService;
        _formService = formService;
        _sysConfigService = sysConfigService;
        _connectionStrings = connectionOptions.Value;
        _tenant = tenantOptions.Value;
        _sqlSugarClient = (SqlSugarClient)sqlSugarClient;
        _httpContextAccessor = httpContextAccessor;
        _cacheManager = cacheManager;
        _userManager = userManager;
        _eventPublisher = eventPublisher;
    }

    /// <summary>
    /// 获取图形验证码.
    /// </summary>
    /// <param name="codeLength">验证码长度.</param>
    /// <param name="timestamp">时间戳.</param>
    /// <returns></returns>
    [HttpGet("ImageCode/{codeLength}/{timestamp}")]
    [AllowAnonymous]
    [IgnoreLog]
    [NonUnify]
    public async Task<IActionResult> GetCode(int codeLength, string timestamp)
    {
        return new FileContentResult(await _captchaHandler.CreateCaptchaImage(timestamp, 120, 40, codeLength > 0 ? codeLength : 4), "image/jpeg");
    }

    #region Get

    /// <summary>
    /// 首次登录 根据账号获取数据库配置.
    /// </summary>
    /// <param name="account">账号.</param>
    [HttpGet("getConfig/{account}")]
    [AllowAnonymous]
    [IgnoreLog]
    public async Task<dynamic> GetConfigCode(string account)
    {
        if (_tenant.MultiTenancy)
        {
            string tenantDbName = _connectionStrings.DBName;
            string tenantId = _connectionStrings.ConfigId;
            string tenantAccout = string.Empty;

            //分割账号
            var tenantAccount = account.Split('@');
            tenantId = tenantAccount.FirstOrDefault();

            if (tenantAccount.Length == 1) account = "admin";
            else account = tenantAccount[1];

            tenantAccout = account;

            var interFace = string.Format("{0}{1}", _tenant.MultiTenancyDBInterFace, tenantId);
            var response = await interFace.GetAsStringAsync();
            var result = response.ToObject<RESTfulResult<TenantInterFaceOutput>>();
            if (result.code != 200)
                throw Oops.Oh(result.msg);
            else if (result.data.dotnet == null)
                throw Oops.Oh(ErrorCode.D1025);
            else
                tenantDbName = result.data.dotnet;

            _sqlSugarClient.AddConnection(new ConnectionConfig()
            {
                DbType = (DbType)Enum.Parse(typeof(DbType), _connectionStrings.DBType),
                ConfigId = tenantId, // 设置库的唯一标识
                IsAutoCloseConnection = true,
                ConnectionString = string.Format(_connectionStrings.DefaultConnection, tenantDbName)
            });
            _sqlSugarClient.ChangeDatabase(tenantId);
        }

        // 验证连接是否成功
        if (!_sqlSugarClient.Ado.IsValidConnection())
        {
            throw Oops.Oh(ErrorCode.D1032);
        }

        // 读取配置文件
        var array = new Dictionary<string, object>();
        var sysConfigData = await _sqlSugarClient.Queryable<SysConfigEntity>().Where(x => x.Category.Equals("SysConfig") && (x.Key.Equals("enableverificationcode") || x.Key.Equals("verificationcodenumber"))).ToListAsync();
        foreach (var item in sysConfigData)
        {
            if (!array.ContainsKey(item.Key)) array.Add(item.Key, item.Value);
        }

        var sysConfig = array.ToObject<SysConfigModel>();

        // 返回给前端 是否开启验证码 和 验证码长度
        return new { enableVerificationCode = sysConfig.enableVerificationCode, verificationCodeNumber = sysConfig.verificationCodeNumber > 0 ? sysConfig.verificationCodeNumber : 4 };
    }

    /// <summary>
    /// 获取当前登录用户信息.
    /// </summary>
    /// <param name="type">Web和App</param>
    /// <returns></returns>
    [HttpGet("CurrentUser")]
    public async Task<dynamic> GetCurrentUser(string type)
    {
        if (type.IsNullOrEmpty()) type = "Web"; // 默认为Web端菜单目录

        var userId = _userManager.UserId;
        var tenantId = _userManager.TenantId;
        var tenantDbName = _userManager.TenantDbName;

        var loginOutput = new CurrentUserOutput();
        loginOutput.userInfo = await _userManager.GetUserInfo();

        // 菜单
        loginOutput.menuList = await _moduleService.GetUserTreeModuleList(type);

        var currentUserModel = new CurrentUserModelOutput();
        currentUserModel.moduleList = await _moduleService.GetUserModueList(type);
        currentUserModel.buttonList = await _moduleButtonService.GetUserModuleButtonList();
        currentUserModel.columnList = await _columnService.GetUserModuleColumnList();
        currentUserModel.resourceList = await _moduleDataAuthorizeSchemeService.GetResourceList();
        currentUserModel.formList = await _formService.GetUserModuleFormList();

        // 权限信息
        var permissionList = new List<PermissionModel>();
        currentUserModel.moduleList.ForEach(menu =>
        {
            var permissionModel = new PermissionModel();
            permissionModel.modelId = menu.id;
            permissionModel.moduleName = menu.fullName;
            permissionModel.button = currentUserModel.buttonList.FindAll(t => t.moduleId.Equals(menu.id)).Adapt<List<FunctionalButtonAuthorizeModel>>();
            permissionModel.column = currentUserModel.columnList.FindAll(t => t.moduleId.Equals(menu.id)).Adapt<List<FunctionalColumnAuthorizeModel>>();
            permissionModel.form = currentUserModel.formList.FindAll(t => t.moduleId.Equals(menu.id)).Adapt<List<FunctionalFormAuthorizeModel>>();
            permissionModel.resource = currentUserModel.resourceList.FindAll(t => t.moduleId.Equals(menu.id)).Adapt<List<FunctionalResourceAuthorizeModel>>();
            permissionList.Add(permissionModel);
        });

        loginOutput.permissionList = permissionList;

        // 系统配置信息
        var sysInfo = await _sysConfigService.GetInfo();
        loginOutput.sysConfigInfo = sysInfo.Adapt<SysConfigInfo>();

        return loginOutput;
    }

    /// <summary>
    /// 退出.
    /// </summary>
    /// <returns></returns>
    [HttpGet("Logout")]
    public async Task Logout()
    {
        var httpContext = _httpContextAccessor.HttpContext;
        httpContext.SignoutToSwagger();

        // 清除IM中的webSocket
        var list = await GetOnlineUserList();
        if (list != null)
        {
            var onlineUser = list.Find(it => it.tenantId == _userManager.TenantId && it.userId == _userManager.UserId);
            if (onlineUser != null)
            {
                list.RemoveAll((x) => x.connectionId == onlineUser.connectionId);
                await SetOnlineUserList(list);
            }
        }

        await DelUserInfo();
    }

    #endregion

    #region POST

    /// <summary>
    /// 用户登录.
    /// </summary>
    /// <param name="input">登录输入参数.</param>
    /// <returns></returns>
    [HttpPost("Login")]
    [Consumes("application/x-www-form-urlencoded")]
    [AllowAnonymous]
    [IgnoreLog]
    public async Task<dynamic> Login([FromForm] LoginInput input)
    {
        string tenantDbName = _connectionStrings.DBName;
        string tenantId = _connectionStrings.ConfigId;
        string tenantAccout = string.Empty;

        if (_tenant.MultiTenancy)
        {
            // 分割账号
            var tenantAccount = input.account.Split('@');
            tenantId = tenantAccount.FirstOrDefault();
            if (tenantAccount.Length == 1)
                input.account = "admin";
            else
                input.account = tenantAccount[1];
            tenantAccout = input.account;

            var interFace = string.Format("{0}{1}", _tenant.MultiTenancyDBInterFace, tenantId);
            var response = await interFace.GetAsStringAsync();
            var result = response.ToObject<RESTfulResult<TenantInterFaceOutput>>();
            if (result.code != 200)
                throw Oops.Oh(result.msg);
            else if (result.data.dotnet == null)
                throw Oops.Oh(ErrorCode.D1025);
            else
                tenantDbName = result.data.dotnet;

            _sqlSugarClient.AddConnection(new ConnectionConfig()
            {
                DbType = (DbType)Enum.Parse(typeof(DbType), _connectionStrings.DBType),
                ConfigId = tenantId, // 设置库的唯一标识
                IsAutoCloseConnection = true,
                ConnectionString = string.Format(_connectionStrings.DefaultConnection, tenantDbName)
            });
            _sqlSugarClient.ChangeDatabase(tenantId);
        }

        // 验证连接是否成功
        if (!_sqlSugarClient.Ado.IsValidConnection())
        {
            throw Oops.Oh(ErrorCode.D1032);
        }

        // 读取配置文件
        var array = new Dictionary<string, string>();
        var sysConfigData = await _sqlSugarClient.Queryable<SysConfigEntity>().Where(x => x.Category.Equals("SysConfig")).ToListAsync();
        foreach (var item in sysConfigData)
        {
            if (!array.ContainsKey(item.Key)) array.Add(item.Key, item.Value);
        }

        var sysConfig = array.ToObject<SysConfigByOAuthModel>();

        // 验证码不为空时
        if (!string.IsNullOrEmpty(input.code))
        {
            if (sysConfig.enableVerificationCode)
            {
                if (string.IsNullOrWhiteSpace(input.timestamp) || string.IsNullOrWhiteSpace(input.code))
                    throw Oops.Oh(ErrorCode.D1029);
                string imageCode = await GetCode(input.timestamp);
                if (imageCode.IsNullOrEmpty())
                    throw Oops.Oh(ErrorCode.D1030);
                if (!input.code.ToLower().Equals(imageCode.ToLower()))
                    throw Oops.Oh(ErrorCode.D1029);
            }
        }

        // 根据用户账号获取用户秘钥
        var user = await _sqlSugarClient.Queryable<UserEntity>().SingleAsync(it => it.Account.Equals(input.account) && it.DeleteMark == null);
        _ = user ?? throw Oops.Oh(ErrorCode.D5002);

        // 验证账号是否未被激活
        if (user.EnabledMark == null) throw Oops.Oh(ErrorCode.D1018);

        // 验证账号是否被禁用
        if (user.EnabledMark == 0) throw Oops.Oh(ErrorCode.D1019);

        // 验证账号是否被删除
        if (user.DeleteMark == 1) throw Oops.Oh(ErrorCode.D1017);

        // 是否延迟登录
        if (sysConfig.lockType.Equals(ErrorStrategy.Delay) && user.UnLockTime.IsNullOrEmpty())
        {
            if (user.UnLockTime > DateTime.Now)
            {
                int unlockTime = ((user.UnLockTime - DateTime.Now)?.TotalMinutes).ParseToInt();
                if (unlockTime < 1) unlockTime = 1;
                throw Oops.Oh(ErrorCode.D1027, unlockTime);
            }
            else if (user.UnLockTime <= DateTime.Now)
            {
                user.EnabledMark = 1;
                user.LogErrorCount = 0;
                await _sqlSugarClient.Updateable(user).UpdateColumns(it => new { it.LogErrorCount, it.EnabledMark }).ExecuteCommandAsync();
            }
        }

        // 是否 延迟登录
        if (sysConfig.lockType.Equals(ErrorStrategy.Delay) && user.UnLockTime.IsNotEmptyOrNull() && user.UnLockTime > DateTime.Now)
        {
            int? t3 = (user.UnLockTime - DateTime.Now)?.TotalMinutes.ParseToInt();
            if (t3 < 1) t3 = 1;
            throw Oops.Oh(ErrorCode.D1027, t3?.ToString());
        }

        if (sysConfig.lockType.Equals(ErrorStrategy.Delay) && user.UnLockTime.IsNotEmptyOrNull() && user.UnLockTime <= DateTime.Now)
        {
            user.EnabledMark = 1;
            user.LogErrorCount = 0;
            await _sqlSugarClient.Updateable(user).UpdateColumns(it => new { it.LogErrorCount, it.EnabledMark }).ExecuteCommandAsync();
        }

        // 是否锁定
        if (user.EnabledMark == 2) throw Oops.Oh(ErrorCode.D1031);

        // 获取加密后的密码
        var encryptPasswod = MD5Encryption.Encrypt(input.password + user.Secretkey);

        // 账户密码是否匹配
        var userAnyPwd = await _sqlSugarClient.Queryable<UserEntity>().SingleAsync(u => u.Account == input.account && u.Password == encryptPasswod && u.DeleteMark == null);
        if (userAnyPwd.IsNullOrEmpty())
        {
            // 如果是密码错误 记录账号的密码错误次数
            await UpdateErrorLog(user, sysConfig);
        }
        else
        {
            // 清空记录错误次数
            userAnyPwd.LogErrorCount = 0;

            // 解除锁定
            userAnyPwd.EnabledMark = 1;
            await _sqlSugarClient.Updateable(userAnyPwd).UpdateColumns(it => new { it.LogErrorCount, it.EnabledMark }).ExecuteCommandAsync();
        }

        _ = userAnyPwd ?? throw Oops.Oh(ErrorCode.D1000);

        // app权限验证
        if (UserAgent.isMobileBrowser && user.IsAdministrator == 0 && !ExistRoleByApp(user.RoleId))
            throw Oops.Oh(ErrorCode.D1022);

        // 登录成功时 判断单点登录信息
        int whitelistSwitch = Convert.ToInt32(sysConfig.whitelistSwitch);
        string whiteListIp = sysConfig.whiteListIp;
        if (whitelistSwitch.Equals(1) && user.IsAdministrator.Equals(0))
        {
            if (!whiteListIp.Split(",").Contains(NetHelper.Ip))
                throw Oops.Oh(ErrorCode.D9002);
        }

        // token过期时间
        long tokenTimeout = sysConfig.tokenTimeout;

        // 生成Token令牌
        string accessToken = JWTEncryption.Encrypt(
                new Dictionary<string, object>
                {
                    { ClaimConst.CLAINMUSERID, userAnyPwd.Id },
                    { ClaimConst.CLAINMACCOUNT, userAnyPwd.Account },
                    { ClaimConst.CLAINMREALNAME, userAnyPwd.RealName },
                    { ClaimConst.CLAINMADMINISTRATOR, userAnyPwd.IsAdministrator },
                    { ClaimConst.TENANTID, tenantId },
                    { ClaimConst.TENANTDBNAME, tenantDbName },
                    { ClaimConst.SINGLELOGIN, (int)sysConfig.singleLogin }
                }, tokenTimeout);

        // 设置Swagger自动登录
        _httpContextAccessor.HttpContext.SigninToSwagger(accessToken);

        // 设置刷新Token令牌
        _httpContextAccessor.HttpContext.Response.Headers["x-access-token"] = JWTEncryption.GenerateRefreshToken(accessToken, 30); // 生成刷新Token令牌

        var ip = NetHelper.Ip;

        // 修改用户登录信息
        await _eventPublisher.PublishAsync(new UserEventSource("User:UpdateUserLogin", tenantId, tenantDbName, new UserEntity
        {
            Id = user.Id,
            FirstLogIP = user.FirstLogIP ?? ip,
            FirstLogTime = user.FirstLogTime ?? DateTime.Now,
            PrevLogTime = user.LastLogTime,
            PrevLogIP = user.LastLogIP,
            LastLogTime = DateTime.Now,
            LastLogIP = ip,
            LogSuccessCount = user.LogSuccessCount + 1
        }));

        // 增加登录日志
        await _eventPublisher.PublishAsync(new LogEventSource("Log:CreateVisLog", tenantId, tenantDbName, new SysLogEntity
        {
            Id = SnowflakeIdHelper.NextId(),
            UserId = user.Id,
            UserName = user.RealName,
            Category = 1,
            IPAddress = ip,
            Abstracts = "登录成功",
            PlatForm = string.Format("{0}-{1}", UserAgent.GetSystem(), UserAgent.GetBrowser()),
            CreatorTime = DateTime.Now
        }));

        return new
        {
            theme = user.Theme == null ? "classic" : user.Theme,
            token = string.Format("Bearer {0}", accessToken)
        };
    }

    /// <summary>
    /// 锁屏解锁登录.
    /// </summary>
    /// <param name="input">登录输入参数.</param>
    /// <returns></returns>
    [HttpPost("LockScreen")]
    public async Task LockScreen([FromBody] LockScreenInput input)
    {
        // 根据用户账号获取用户秘钥
        var secretkey = (await _userRepository.FirstOrDefaultAsync(u => u.Account == input.account && u.DeleteMark == null)).Secretkey;

        // 获取加密后的密码
        var encryptPasswod = MD5Encryption.Encrypt(input.password + secretkey);

        var user = await _userRepository.FirstOrDefaultAsync(u => u.Account == input.account && u.Password == encryptPasswod && u.DeleteMark == null);
        _ = user ?? throw Oops.Oh(ErrorCode.D1000);
    }

    #endregion

    #region PrivateMethod

    /// <summary>
    /// 获取验证码.
    /// </summary>
    /// <param name="timestamp">时间戳.</param>
    /// <returns></returns>
    private async Task<string> GetCode(string timestamp)
    {
        string cacheKey = string.Format("{0}{1}", CommonConst.CACHEKEYCODE, timestamp);
        return await _cacheManager.GetAsync<string>(cacheKey);
    }

    /// <summary>
    /// 判断app用户角色是否存在且有效.
    /// </summary>
    /// <param name="roleIds"></param>
    /// <returns></returns>
    private bool ExistRoleByApp(string roleIds)
    {
        if (roleIds.IsEmpty())
            return false;
        var roleIdList1 = roleIds.Split(",").ToList();
        var roleIdList2 = _sqlSugarClient.Queryable<RoleEntity>().Where(x => x.DeleteMark == null && x.EnabledMark == 1).Select(x => x.Id).ToList();
        return roleIdList1.Intersect(roleIdList2).ToList().Count > 0;
    }

    /// <summary>
    /// 记录密码错误次数.
    /// </summary>
    /// <param name="entity">用户实体.</param>
    /// <param name="sysConfigOutput">系统配置输出.</param>
    /// <returns></returns>
    private async Task UpdateErrorLog(UserEntity entity, SysConfigByOAuthModel sysConfigOutput)
    {
        if (entity != null)
        {
            if (entity.EnabledMark.Equals(1) && !entity.Account.ToLower().Equals("admin") && sysConfigOutput.lockType > 0 && sysConfigOutput.passwordErrorsNumber > 2)
            {

                switch (sysConfigOutput.lockType)
                {
                    case ErrorStrategy.Lock:
                        entity.EnabledMark = entity.LogErrorCount >= sysConfigOutput.passwordErrorsNumber - 1 ? 2 : 1;
                        break;
                    case ErrorStrategy.Delay:
                        entity.UnLockTime = entity.LogErrorCount >= sysConfigOutput.passwordErrorsNumber - 1 ? DateTime.Now.AddMinutes(sysConfigOutput.lockTime) : null;
                        entity.EnabledMark = entity.LogErrorCount >= sysConfigOutput.passwordErrorsNumber - 1 ? 2 : 1;
                        break;
                }

                entity.LogErrorCount++;

                await _sqlSugarClient.Updateable(entity).UpdateColumns(it => new { it.EnabledMark, it.UnLockTime, it.LogErrorCount }).ExecuteCommandAsync();
            }
        }
    }

    /// <summary>
    /// 获取在线用户列表.
    /// </summary>
    /// <returns></returns>
    public async Task<List<UserOnlineModel>> GetOnlineUserList()
    {
        string cacheKey = string.Format("{0}{1}", CommonConst.CACHEKEYONLINEUSER, _userManager.TenantId);
        return await _cacheManager.GetAsync<List<UserOnlineModel>>(cacheKey);
    }

    /// <summary>
    /// 保存在线用户列表.
    /// </summary>
    /// <param name="onlineList">在线用户列表.</param>
    /// <returns></returns>
    public async Task<bool> SetOnlineUserList(List<UserOnlineModel> onlineList)
    {
        string cacheKey = string.Format("{0}{1}", CommonConst.CACHEKEYONLINEUSER, _userManager.TenantId);
        return await _cacheManager.SetAsync(cacheKey, onlineList);
    }

    /// <summary>
    /// 删除用户登录信息缓存.
    /// </summary>
    public async Task<bool> DelUserInfo()
    {
        string cacheKey = string.Format("{0}{1}_{2}", CommonConst.CACHEKEYUSER, _userManager.TenantId, _userManager.UserId);
        return await _cacheManager.DelAsync(cacheKey);
    }

    #endregion
}